﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Test;

namespace puzzle
{
    public class RectField
    {
        public int Width { get; set; }
        public int Height { get; set; }
        private Tile[,] tiles;
        private HashSet<Box> boxes;

        private Random random;
        private List<int> loopBuffer;
        private static int loopBufferSize = 100;
        private static int maxMotifLength = 15;

        public RectField(int width, int height, int maxBoxSize, int minMeanBoxSize)
        {
            Width = width;
            Height = height;
            tiles = new Tile[width, height];
            
            int maxLoop = width * height * 10;



            int loop;
            do
            {
                init();
                loop = 0;
                while ((width * height / boxes.Count < minMeanBoxSize || getBigBoxes(maxBoxSize).Count > 0) && loop < maxLoop)
                {
                    //Console.WriteLine(loop + " : " + width * height / boxes.Count + " / " + getBigBoxes(maxBoxSize).Count);
                    if (getBigBoxes(maxBoxSize).Count > 0)
                    {
                        Box box = null;
                        Box[] array = getBigBoxes(maxBoxSize).ToArray();
                        if (array.Length > 0)
                        {
                            int index = random.Next(array.Length);
                            box = array[index];
                        }

                        fission(box);
                    }
                    else if (width * height / boxes.Count < minMeanBoxSize)
                    {
                        Box box = null;
                        Box[] array = getSmallBoxes(minMeanBoxSize).ToArray();
                        if (array.Length > 0)
                        {
                            int index = random.Next(array.Length);
                            box = array[index];
                        }

                        Box res = null;
                        while (res == null)
                        {
                            int dir = random.Next(4);
                            switch (dir)
                            {
                                case 0: res = fusionRight(box); break;
                                case 1: res = fusionLeft(box); break;
                                case 2: res = fusionDown(box); break;
                                case 3: res = fusionUp(box); break;
                            }
                            if (isLooping() || !isValid())
                            {
                                loopBuffer.Clear();
                                break;
                            }
                        }
                    }
                    loop++;

                    if (!isValid())
                    {
                        init();
                        loop = 0;
                    }
                }
            } while (loop == maxLoop);
        }

        public void init()
        {
            Box.init();
            boxes = new HashSet<Box>();
            loopBuffer = new List<int>();
            random = new Random();

            for (int i = 0; i < Width; i++)
            {
                for (int j = 0; j < Height; j++)
                {
                    tiles[i, j] = new Tile(i, j);
                    boxes.Add(new Box(tiles[i, j]));
                }
            }
        }

        public Box getBox(int x, int y)
        {
            foreach (Box box in boxes)
            {
                if (x >= box.X && y >= box.Y && x < box.X + box.Width && y < box.Y + box.Height)
                {
                    return box;
                }
            }
            return null;
        }

        public Box fusionRight(Box box){
            loopBuffer.Insert(0, 0);
            if (loopBuffer.Count > loopBufferSize) loopBuffer.RemoveAt(loopBufferSize);
            if (isLooping()) return null;

            Box rightBox = getBox(box.X + box.Width, box.Y);
            if (rightBox == null) return null;

            if (rightBox.Y != box.Y) return fusionLeft(rightBox);

            while (box.Height > rightBox.Height)
            {
                rightBox = fusionDown(rightBox);
                if (rightBox == null) return null; // looping
            }

            if (box.Height < rightBox.Height)
            {
                return fusionLeft(rightBox);
            }
            else
            {
                box.Width = box.Width + rightBox.Width;
                boxes.Remove(rightBox);
                return box;
            }

        }

        public Box fusionDown(Box box)
        {
            loopBuffer.Insert(0, 1);
            if (loopBuffer.Count > loopBufferSize) loopBuffer.RemoveAt(loopBufferSize);
            if (isLooping()) return null;

            Box downBox = getBox(box.X, box.Y + box.Height);
            if (downBox == null) return null;

            if (downBox.X != box.X) return fusionUp(downBox);

            while (box.Width > downBox.Width)
            {
                downBox = fusionRight(downBox);
                if (downBox == null) return null; // looping
            }

            if (box.Width < downBox.Width)
            {
                return fusionUp(downBox);
            }
            else
            {
                box.Height = box.Height + downBox.Height;
                boxes.Remove(downBox);
                return box;
            }
        }

        public Box fusionLeft(Box box)
        {
            loopBuffer.Insert(0, 2);
            if (loopBuffer.Count > loopBufferSize) loopBuffer.RemoveAt(loopBufferSize);
            if (isLooping()) return null;

            Box leftBox = getBox(box.X - 1, box.Y);
            if (leftBox == null) return null;

            if (leftBox.Y != box.Y) return fusionRight(leftBox);

            while (box.Height > leftBox.Height)
            {
                leftBox = fusionDown(leftBox);
                if (leftBox == null) return null; // looping
            }

            return fusionRight(leftBox);

            
        }

        public Box fusionUp(Box box)
        {
            loopBuffer.Insert(0, 3);
            if (loopBuffer.Count > loopBufferSize) loopBuffer.RemoveAt(loopBufferSize);
            if (isLooping()) return null;

            Box upBox = getBox(box.X, box.Y-1);
            if (upBox == null) return null;

            if (upBox.X != box.X) return fusionDown(upBox);

            while (box.Width > upBox.Width)
            {
                upBox = fusionRight(upBox);
                if (upBox == null) return null; // looping
            }

            return fusionDown(upBox);
        }

        public void fission(Box box)
        {
            if (box.Width > 1 || box.Height > 1)
            {
                if (box.Width > box.Height)
                {
                    int oldWidth = box.Width;
                    int newWidth = random.Next(1, box.Width - 1);
                    box.Width = newWidth;
                    Box newBox = new Box(tiles[box.X + newWidth, box.Y]);
                    newBox.Width = oldWidth - newWidth;
                    newBox.Height = box.Height;
                    boxes.Add(newBox);
                }
                else
                {
                    int oldHeight = box.Height;
                    int newHeight = random.Next(1, box.Height - 1);
                    box.Height = newHeight;
                    Box newBox = new Box(tiles[box.X, box.Y + newHeight]);
                    newBox.Height = oldHeight - newHeight;
                    newBox.Width = box.Width;
                    boxes.Add(newBox);
                }
            }
        }

        public HashSet<Box> getBoxes(){
            return boxes;
        }

        public bool isLooping(){
            if (loopBuffer.Count != loopBufferSize) return false;

            for (int length = 2; length <= maxMotifLength; length++)
            {
                int k = length;
                int loopCounter = 0;
                List<int> motif = loopBuffer.GetRange(0, length);

                while (k + length < loopBuffer.Count)
                {
                    List<int> sublist = loopBuffer.GetRange(k, length);
                    if (sublist.SequenceEqual(motif)) loopCounter++;
                    k += length;
                }
                if (loopCounter > loopBufferSize / (length * 2)) return true;
            }

            return false;
        }

        public HashSet<Box> getSmallBoxes(int threshold)
        {
            HashSet<Box> res = new HashSet<Box>();
            foreach (Box box in boxes)
            {
                if (box.Width * box.Height <= threshold)
                {
                    res.Add(box);
                }
            }
            return res;
        }

        public HashSet<Box> getBigBoxes(int threshold)
        {
            HashSet<Box> res = new HashSet<Box>();
            foreach (Box box in boxes)
            {
                if (box.Width * box.Height > threshold)
                {
                    res.Add(box);
                }
            }
            return res;
        }

        public bool isValid()
        {
            for (int i = 0; i < Width; i++)
            {
                for (int j = 0; j < Height; j++)
                {
                    if (getBox(i, j) == null) return false;
                }
            }
            return true;
        }

    }
}
